# 面向对象
# 封装、继承、多态

# 定义类
# 封装方法和变量（属性）
# object是所有类的基类 相当于Java中的Object
class Person(object):
    # 构造函数
    # 类中所有的方法的第一个参数都是self：代表对象本身 相当于Java中的this
    def __init__(self, id, name, gender):
        # 定义了类的属性并进行赋值
        self.id = id
        self.name = name
        self.gender = gender
        # 如果定义的属性不想被直接获取 可以在命名的时候以两个下划线开头
        self.__secret = '这是一个秘密'

    def info(self):
        print("Person", self.id, self.name, self.gender)

    def run(self):
        print("我会跑")

    # 相当于Java中的toString方法 在使用print函数打印该类的对象时会自动调用
    # 重写object中的方法
    def __str__(self):
        return "Person类的对象 %s" % self.name


# 创建对象
zs = Person('001', '张三', '男')
print(zs)
print(zs.id)
print(zs.name)
print(zs.gender)
# print(zs.__secret) # 不能直接获取 相当于进行了 "封装"
# Python中没有严格意义上的封装 更多地是靠编程者良好的编程习惯去约束
# 还是可以通过 _类名+属性名 去将隐藏的属性取出来
print(zs._Person__secret)

zs.info()


class Teacher(Person):
    def __init__(self, id, name, gender):
        # 调用父类的构造方法
        super(Teacher, self).__init__(id, name, gender)
        self.professional = 'compute'

    def info(self):
        print("Teacher", self.id, self.name, self.gender, self.professional)


t1 = Teacher("张老师", "24", "男")
print(t1.id)
print(t1.name)
print(t1.professional)
t1.info()
t1.run()


class Student(Person):
    def __init__(self, id, name, gender):
        # 调用父类的构造方法
        super(Student, self).__init__(id, name, gender)
        self.skill = 'play game'

    def info(self):
        print("Student", self.id, self.name, self.gender, self.skill)

    def run(self):
        print("我跑的很快")


stu1 = Student("李四", "20", "男")


# 定义一个方法接收Person类型的参数 然后对run方法调用两次
# 多态：父类引用指向子类对象
def runTwice(person: Person):
    person.run()
    person.run()


runTwice(zs)
runTwice(t1)
runTwice(stu1)


# 鸭子类型：只要长得像鸭子那么就是鸭子
# 更多的是关注变量有没有方法 并不会对变量做类型的检查
class Duck():
    def run(self):
        print("鸭子也会跑")


dk = Duck()

runTwice(dk)


class Dog():
    pass


dog = Dog()


def run():
    print("狗也会跑")


# 猴子补丁
dog.run = run

runTwice(dog)
